#pragma once

#include <utility>
#include <limits>
#include <type_traits>
#include <cstddef>
#include <tuple>

#include "pack.h"

namespace mdtx::online
{
    namespace detail
    {
        // TODO: support vectorised type
        template <typename T, bool is_float>
        struct kahan_impl;

        template <typename T>
        struct kahan_impl<T, true>
        {
            mutable T c;

            void operator()(T &sum, T x) const
            {
                volatile T y = x - c;
                volatile T t = sum + y;
                c = (t - sum) - y;
                sum = t;
            }

            void reset() const
            {
                c = 0;
            }
        };

        template <typename T>
        struct kahan_impl<T, false>
        {
            void operator()(T &sum, T x) const
            {
                sum += x;
            }

            void reset() const
            {
            }
        };

        template <typename, typename>
        struct tuple_join
        {
        };

        template <typename... L, typename... R>
        struct tuple_join<std::tuple<L...>, std::tuple<R...>>
        {
            using type = std::tuple<L..., R...>;
        };

        template <typename T, size_t N>
        struct ntuple_impl
        {
            using left = typename ntuple_impl<T, N / 2>::type;
            using right = typename ntuple_impl<T, N / 2 + N % 2>::type;
            using type = typename tuple_join<left, right>::type;
        };

        template <typename T>
        struct ntuple_impl<T, 1>
        {
            using type = std::tuple<T>;
        };

        template <typename T>
        struct ntuple_impl<T, 0>
        {
            using type = std::tuple<>;
        };

        template <typename T1, typename T2, size_t N1, size_t N2>
        struct bind_ntuple_impl
        {
            using t1_part = typename ntuple_impl<T1, N1>::type;
            using t2_part = typename ntuple_impl<T2, N2>::type;
            using type = typename tuple_join<t1_part, t2_part>::type;
        };
    }

    /**
     * @brief Kahan summation functor
     *
     * @tparam T data type
     */
    template <typename T>
    using kahan = detail::kahan_impl<T, std::is_floating_point_v<T>>;

    template <typename T, size_t N>
    using ntuple = typename detail::ntuple_impl<T, N>::type;

    template <typename T1, typename T2, size_t N1, size_t N2>
    using bind_ntuple = typename detail::bind_ntuple_impl<T1, T2, N1, N2>::type;

    template <typename Time, typename T, size_t N>
    using time_ntuple = typename detail::bind_ntuple_impl<Time, T, 1, N>::type;

    /**
     * @brief Return a small number of T
     *
     * @tparam T
     * @return constexpr T
     */
    template <typename T>
    constexpr inline T small_number(T)
    {
        if constexpr (std::is_floating_point_v<T>)
        {
            return std::numeric_limits<T>::min();
        }
        else
        {
            return T{};
        }
    }

    /**
     * @brief Sign of value
     *
     * @tparam T
     * @param v
     * @return int
     */
    template <typename T>
    constexpr inline int sign(T v)
    {
        return (T{} < v) - (v < T{});
    }

    /**
     * @brief Convenient wrapper for multiple buf.push_back()
     *
     * @tparam C
     * @tparam Args
     * @param buf a container supports push_back()
     * @param args data to push back
     */
    template <typename C, typename... Args>
    inline void bpush(C &buf, Args &&...args)
    {
        (buf.push_back(std::forward<Args>(args)), ...);
    }

    /**
     * @brief Convenient wrapper for multiple buf.push_front()
     *
     * @tparam C
     * @tparam Args
     * @param buf a container supports push_back()
     * @param args data to push back
     */
    template <typename C, typename... Args>
    inline void fpush(C &buf, Args &&...args)
    {
        // right associative op to reverse args order
        [[maybe_unused]] int tmp;
        (tmp = ... = (buf.push_front(std::forward<Args>(args)), 0));
    }

    namespace detail
    {
        template <typename C, typename TUPLE, size_t... Is>
        inline void call_bpush(C &buf, TUPLE const &tup, std::integer_sequence<size_t, Is...>)
        {
            bpush(buf, std::get<Is>(tup)...);
        }

        template <typename C, typename TUPLE, size_t... Is>
        inline void call_fpush(C &buf, TUPLE const &tup, std::integer_sequence<size_t, Is...>)
        {
            fpush(buf, std::get<Is>(tup)...);
        }
    } // namespace detail

    template <typename C, typename... T>
    inline void bpush_tup(C &buf, std::tuple<T...> const &tup)
    {
        constexpr make_index_range<0, sizeof...(T)> idx{};
        detail::call_bpush(buf, tup, idx);
    }

    template <typename C, typename... T>
    inline void fpush_tup(C &buf, std::tuple<T...> const &tup)
    {
        constexpr make_index_range<0, sizeof...(T)> idx{};
        detail::call_fpush(buf, tup, idx);
    }
}
